Skip to content

Comments

Finilazing EPR support#39

Merged
elagil merged 49 commits intoelagil:mainfrom
okhsunrog:finished_epr_support
Feb 5, 2026
Merged

Finilazing EPR support#39
elagil merged 49 commits intoelagil:mainfrom
okhsunrog:finished_epr_support

Conversation

@okhsunrog
Copy link
Contributor

@okhsunrog okhsunrog commented Dec 8, 2025

Continuing the work in #37
Closes #36

Tested on STM32G431.

elagil and others added 19 commits September 5, 2025 15:18
- policy engine states for enter/exit of EPR mode
- consistent naming of modules
- extend DPM
Implements USB PD 3.x chunked extended message support per Section 6.13.
Extended messages can exceed the standard packet size and are split into
chunks of up to 26 bytes each.

- Add ChunkedMessageAssembler for assembling multi-chunk messages
- Add ExtendedHeader bitfield for extended message headers
- Extend protocol layer to handle chunked message assembly and disassembly
- Add parse_extended_chunk and parse_extended_payload helpers
- Increase MAX_MESSAGE_SIZE to 272 bytes to support extended messages
- Add extended message receive buffer and chunking state tracking
- Refactor message ACK handling into separate handle_rx_ack method
Implements Extended Power Range (EPR) message types per USB PD 3.x spec.
EPR mode enables power delivery beyond standard 100W limit.

- Add EPR_Request data message type with RDO + PDO pair
- Add EPR_Mode data message parsing and encoding
- Add parse_raw_pdo helper to deduplicate PDO parsing logic
- Extend SourceCapabilities to support up to 16 PDOs (EPR requires more)
- Add message_type() and num_objects() helpers to PowerSource
- Rename find_pps_voltage to find_augmented_pdo (handles both PPS and EPR AVS)
- Fix message_type() to check extended bit before num_objects (EPR extended
  messages can have data objects)
- Add ExtendedControl message parsing
- Update PDO_SIZE constant for clarity
Implements the EPR mode state machine in the sink policy engine, completing
the EPR support. This enables the sink to enter EPR mode, request EPR power
levels, and maintain EPR mode with keep-alive messages.

- Implement EPR entry states (_EprSendEntry, _EprEntryWaitForResponse)
- Implement EPR keep-alive periodic timer and state
- Implement EPR exit handling (_EprExitReceived)
- Add EPR mode tracking and PS_TRANSITION timer selection based on mode
- Handle EPR_Request power source selection in state machine
- Add EPR source capabilities reception (extended message)
- Add EPR_Mode data message handling (source exit notification)
- Combine PPS and EPR keep-alive timers using select()
- Reset mode to SPR on startup
- Add transmit_epr_mode and transmit_extended_control_message methods
- Update wait_for_source_capabilities to handle EPR extended messages
This commit completes the EPR (Extended Power Range) support for the USB PD
sink implementation, enabling negotiation up to 240W power delivery.

Key changes:
- Renamed EPR state variants (removed underscore prefixes): EprKeepAlive,
  EprSendExit, EprExitReceived are now fully implemented
- Added ExitEprMode DPM event to enable sink-initiated EPR exit
- Implemented DummySinkEprDevice.request() to select highest EPR PDO (48V)
- Created comprehensive integration test covering full EPR negotiation flow:
  * Phase 1: SPR negotiation (5V-20V)
  * Phase 2: EPR mode entry (Enter→EnterAck→EnterSucceeded)
  * Phase 3: Chunked EPR source capabilities assembly
  * Phase 4: EPR power negotiation (48V @ 5A = 240W)

Technical details:
- EPR request construction: Creates RDO + PDO copy per spec Section 6.4.9
- Test harness properly simulates GoodCRC and control message timing
- All existing tests continue to pass

Spec compliance (USB PD R3.2 Section 6.5.15.1):
- Add SourceCapabilities helpers for spec-compliant PDO access:
  * is_zero_padding() - detect zero-filled entries
  * is_epr_capabilities() - check if EPR message (>7 PDOs)
  * spr_pdos() - positions 1-7 (SPR PDOs only)
  * epr_pdos() - positions 8+ (EPR PDOs only)
- EPR PDOs always start at position 8 per spec

Also includes clippy fixes and nightly rustfmt formatting.
Fix hard reset handling to comply with USB PD Spec R3.2:

PE_SNK_Hard_Reset (Section 8.3.3.3.8):
- Check HardResetCounter before transmitting (not after)
- Transmit Hard Reset Signaling via protocol layer
- Return PortPartnerUnresponsive if counter exceeded

PE_SNK_Transition_to_default (Section 8.3.3.3.9):
- Add DPM notification via new hard_reset() callback
- Reset protocol layer (per spec 6.8.3)
- Exit EPR mode (per spec 6.8.3.2)
- Reset contract to Safe5V
- Clear cached source capabilities

DevicePolicyManager trait:
- Add hard_reset() callback for DPM notification
- Default implementation is no-op for backwards compatibility
@okhsunrog okhsunrog force-pushed the finished_epr_support branch from 13fa0b2 to d84d3f7 Compare December 8, 2025 15:07
- Add SinkCapabilities structure with Fixed, Variable, and Battery PDOs
  per USB PD Spec R3.2 Section 6.4.1.6
- Add sink_capabilities() method to DevicePolicyManager trait with
  default 5V @ 100mA implementation
- Implement proper GiveSinkCap state to respond to Get_Sink_Cap messages
  (previously sent NotSupported which was not spec-compliant)
- Add get_source_cap_pending flag to track pending source cap requests
- Implement hard reset for unrequested Source_Capabilities in EPR mode
  per spec section 8.3.3.3.8
Reorder match arms in error handler so TransitionSink state is checked
before the generic UnexpectedMessage handler. Per USB PD Spec R3.2
Table 6.72, unexpected messages during power transition (PE_SNK_Transition_Sink)
shall trigger Hard Reset, not Soft Reset.

Also improve comments with proper spec references throughout the
error handling logic.
Per USB PD Spec R3.2:

- EPR mode entry failure now triggers Soft Reset (not Hard Reset)
  per sections 8.3.3.26.2.1 and 8.3.3.26.2.2

- Add EPR_Get_Sink_Cap handling: respond with EPR_Sink_Capabilities
  per sections 8.3.3.3.7 and 8.3.3.3.10

- Fix GetSourceCap transition logic: go to EvaluateCapabilities when
  mode matches, Ready otherwise, per section 8.3.3.3.12

- Add EPR PDO position validation: Hard Reset if EPR PDO found in
  positions 1-7 of EPR_Source_Capabilities, per section 8.3.3.3.8
Two spec compliance fixes:

1. wait_for_source_capabilities helper now handles both Source_Capabilities
   and EPR_Source_Capabilities messages. EPR Mode persists through Soft Reset
   (per spec 6.8.3.2), so after a Soft Reset while in EPR Mode, the source
   sends EPR_Source_Capabilities (per spec 6.4.1.2.2). Previously this would
   panic on unreachable!().

2. EprExitReceived state now checks if current contract is for SPR or EPR PDO
   per spec 8.3.3.26.4.2:
   - SPR PDO contract (positions 1-7): transition to WaitForCapabilities
   - EPR PDO contract (positions 8+): trigger Hard Reset

   This is a safety requirement - if at 28V/36V/48V when Exit is received,
   the sink cannot safely continue at that voltage in SPR mode.
Per spec sections 6.6.4.1 and 8.3.3.3.7, after receiving a Wait message
in response to a Request, the sink must wait at least tSinkRequest
(100ms) before re-requesting. This is a Shall requirement.

Changes:
- Add `after_wait` bool to Ready state to track if entered due to Wait
- When entering Ready after Wait, run SinkRequestTimer before allowing
  re-request (transitions to SelectCapability on timeout)
- Distinguish between Wait and Reject responses in SelectCapability
Two bugs fixed:

1. transmit_extended_control_message: Set num_objects to 1 instead of 0.
   Per USB PD spec 6.2.1.1.2, extended messages must have non-zero
   NumDataObjects. ExtendedControl messages are 4 bytes (2-byte extended
   header + 2-byte ECDB), which equals 1 data object.

2. transmit_chunk_request: Add 2 bytes of zero padding after the
   extended header. Per USB PD spec 6.2.1.2, chunked messages must be
   padded to the next 4-byte Data Object boundary. Without this padding,
   the UCPD hardware would read garbage bytes from the DMA buffer.

These fixes ensure compatibility with strict USB PD implementations
(e.g., Anker powerbanks) that validate the entire message format.
Per USB PD spec 6.4.10 Table 6.50, the Data field in EPR_Mode(Enter)
"Shall be set to the EPR Sink Operational PDP". Previously this was
hardcoded to 0.

Changed Event::EnterEprMode to take a Power parameter so the caller
specifies their operational PDP. For example, a 28V × 5A = 140W sink
should pass Power::new::<watt>(140).
Per USB PD spec 6.2.1.2.1, chunked mode is required when either port
partner only supports chunked messages. Most power supplies (and PHYs
like FUSB302) don't support unchunked extended messages.

Changes:
- Set Chunked bit to 1 in extended message headers
- Set unchunked_extended_messages_supported to false in RDOs
- Add warning comments to unchunked_extended_messages_supported field
Demonstrates USB PD Extended Power Range (EPR) negotiation:
- Initial SPR power negotiation with EPR capable flag
- Automatic EPR mode entry when source is EPR capable
- Requesting 28V @ 4A (112W) EPR power
- Pretty-printing source capabilities with PDO details

Based on the embassy-stm32-g431cb example.
- Refactor reset() to reuse new() constructor to prevent sync issues
  Using *self = Self::new() ensures the initialization logic is in one place

- Add ParseError::ParserReuse variant to prevent silent data loss
  Instead of auto-resetting when chunk 0 arrives during assembly, return
  an error forcing explicit state management

- Add new_from_chunk() constructor for convenient assembler initialization
  Allows creating and initializing an assembler from chunk 0 in one call

- Add comprehensive tests for new error cases and API usage patterns

This addresses elagil's concern about reset/new getting out of sync and
improves safety by making parser state management explicit.
- Add ParseError::ChunkOverflow variant with actual and max size
- Validate chunk size before copying (must be <= 26 bytes per spec)
- Return error instead of silently truncating oversized chunks
- Add test case for chunk overflow error

Per USB PD spec, extended message chunks must not exceed 26 bytes.
Previously we silently truncated, now we return an explicit error.
Replace manual byte-by-byte loop with idiomatic extend_from_slice().
More concise and clearer intent.

Note: Chunk gapless validation is already in place at line 215-216,
where we verify chunk_number == self.next_chunk, ensuring sequential
ordering without gaps as required by USB PD spec.
Make ChunkedMessageSender more idiomatic by implementing the Iterator trait.
This allows users to use standard iterator methods like for loops, map, etc.

- Implement Iterator with Item = (ExtendedHeader, &'a [u8])
- Add size_hint() for optimization (exact size known upfront)
- Keep next_chunk() as a convenience wrapper
- Add tests demonstrating iterator usage patterns

Example usage:
  for (ext_header, chunk_data) in sender {
      // process chunk
  }
Now that ChunkedMessageSender implements Iterator, the next_chunk() method
is redundant - users can just call .next() directly. This eliminates code
duplication and makes the API cleaner.

- Removed next_chunk() method (logic is in Iterator::next())
- Updated tests to use .next() instead of .next_chunk()
- Keeps get_chunk() for random access (responding to chunk requests)
Fix import ordering and code formatting to pass CI checks.
- Collapse nested match statements in validate_outgoing_message
- Fix trailing whitespace in power.rs
- Apply rustfmt to all code
Clippy detected that .get::<unit>() already returns u32,
so the explicit casts were redundant.
- Remove unused _transmit_data_message function (dead code with bug)
- Add Default derive to ExtendedHeader, use default() instead of new(0)
- Simplify match block in transmit_chunk_request
- Clarify comment about num_objects for unchunked extended messages
- Add MAX_DATA_MESSAGE_SIZE constant to replace magic number 30
- Simplify EPR PDO search using PowerSource::new_fixed()
@elagil elagil merged commit 020cef5 into elagil:main Feb 5, 2026
1 check passed
@okhsunrog okhsunrog deleted the finished_epr_support branch February 6, 2026 00:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add EPR support

2 participants